use crate::{limits::LimitError, scalar_fn::EvalScalar};

pub fn derivative<T>(func: &T, limit_point: f64) -> Result<f64, LimitError>
where
    T: EvalScalar,
{
    let back_coeff = -0.5f64;
    let forward_coeff = 0.5f64;
    let tol = 1e-8_f64;

    let mut err = 1.0f64;

    let mut dx = 0.1;
    let mut back = limit_point - dx;
    let mut forward = limit_point + dx;
    let mut diff = (back_coeff * func.eval(back) + forward_coeff * func.eval(forward)) / dx;
    let mut prior: f64;

    while err > tol {
        prior = diff;
        dx *= 0.1;
        back = limit_point - dx;
        forward = limit_point + dx;
        diff = (back_coeff * func.eval(back) + forward_coeff * func.eval(forward)) / dx;
        err = (diff - prior).abs();
    }

    Ok(diff)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::math::log;
    use crate::scalar_fn::ScalarFn;

    #[test]
    fn test_derivative_log() {
        let limit_point = 5.0f64;
        let log = ScalarFn::new(log, None);
        let result = derivative(&log, limit_point);
        match result {
            Ok(_) => assert!(true),
            Err(x) => {
                println!("Log derivative failed with: {:?}", x);
                assert!(false);
            }
        }
    }
}
